package com.vibes.actors

import akka.actor.{Actor, ActorRef, Props}
import akka.util.Timeout
import com.typesafe.scalalogging.LazyLogging
import com.vibes.actions._
import com.vibes.utils.VConf
import scala.concurrent.duration._

/*
* This actor is simply a helper to offload work from the Coordinator. The NodeRepoActor
* serves as a repository for all Nodes in the network and occasionally broadcasts messages
* addressed to all of them such as: AnnounceSimulationStart and AnnounceSimulationEnd.
* More importantly, this Actor takes care to register or instantiate all Nodes in the network
* with coordinates on land surface only. To achieve land-only instantiation, we have created
* a list of rectangles represented by lat, lng coordinates on the world’s map that are only
* within land surface and whenever we generate a Node, we pick a rectangle and randomly
* instantiate the node within the coordinates of the rectangle.
* 这个actor只是一个从协调员那里卸载工作的帮助者。NodeRepoActor充当网络中所有节点的存储库，并偶尔广播发往所有节点的消息，
例如：AnnouneSimulationStart和AnnouneSimulationEnd。更重要的是，该参与者负责注册或实例化网络中仅具有陆地表面坐标的所有节点。
为了实现仅陆地实例化，我们创建了一个矩形列表，该列表由世界地图上最新LNG坐标表示，这些矩形仅在陆地表面内，
每当我们生成Node时，我们都会选取一个矩形，并在该矩形的坐标内随机实例化该节点。


* A good opportunity for future work is to enable configuration of Nodes’ distribution on
* the world map, as explained in VIBES Chapter 6.
*/
class NodeRepoActor(discoveryActor: ActorRef, reducerActor: ActorRef) extends Actor with LazyLogging{
  implicit val timeout: Timeout = Timeout(20.seconds)
  private var registeredNodeActors: Set[ActorRef] = Set.empty

  override def preStart(): Unit = {
    logger.debug(s"NodeRegistryActor started ${self.path}")
  }

  override def receive: Receive = {
    case NodeRepoActions.RegisterNode =>
      var isEvil: Option[Boolean] = None
      var isListener: Option[Boolean]=Some(false)
      if (VConf.isAlternativeHistoryAttack && (registeredNodeActors.size < VConf.hashRate.toDouble / 100 * VConf.numberOfNodes)) {
        logger.debug(s"REGISTER EVIL NODE....... ${registeredNodeActors.size}")
        isEvil = Some(true)
      } else if (VConf.isAlternativeHistoryAttack) {
        logger.debug(s"REGISTER NODE....... ${registeredNodeActors.size}")
        isEvil = Some(false)
      } else {
        logger.debug(s"REGISTER NODE1.. ${registeredNodeActors.size}")
      }
      val coordinates = NodeRepoActor.createCoordinatesOnLand()
      val actor = context.actorOf(
        NodeActor.props(context.parent, self, discoveryActor, reducerActor, coordinates._1, coordinates._2, isEvil,isListener))
      registeredNodeActors += actor

    case NodeRepoActions.RegisterListener =>
      var isEvil: Option[Boolean] = None
      isEvil = Some(false)
      var isListener: Option[Boolean]=Some(true)
      logger.debug(s"REGISTER Listener....... ${registeredNodeActors.size}") //后面的size用来显示网络规模，所以没改日志输出的个数
     
      val coordinates = NodeRepoActor.createCoordinatesOnLand()
      val actor = context.actorOf(
        NodeActor.props(context.parent, self, discoveryActor, reducerActor, coordinates._1, coordinates._2, isEvil,isListener))
      registeredNodeActors += actor



    case NodeRepoActions.AnnounceStart(now) =>
      logger.debug("NodeRepoActions.AnnounceStart")
      registeredNodeActors.foreach(_ ! NodeActions.StartSimulation(now)) //下滑线指代foreach循环出来的元素

    case NodeRepoActions.AnnounceNextWorkRequestOnly =>
      registeredNodeActors.foreach(_ ! NodeActions.CastNextWorkRequestOnly)

    case NodeRepoActions.AnnounceNextWorkRequestAndMine(timestamp) =>
      registeredNodeActors.foreach(_ ! NodeActions.CastNextWorkRequestAndMine(timestamp, sender()))

    case NodeRepoActions.AnnounceEnd =>
      registeredNodeActors.foreach(_ ! NodeActions.End)
  }
}

object NodeRepoActor {
  private val coordinateLimits = List(
    // Some coordinates exist multiple times to increase their probability.

    // North America
    Coordinate(60, 68, -140, -120),
    Coordinate(51, 61, -127, -99),
    Coordinate(52, 57, -127, -95),
    Coordinate(45, 53, -118, -88),
    Coordinate(32, 43, -108, -88),
    Coordinate(44, 50, -76, -63),
    // South America
    Coordinate(-10, 2, -76, -53),
    Coordinate(-14, -4, -73, -42),
    Coordinate(-25, -14, -64, -50),
    Coordinate(-30, -25, -69, -53),
    // Africa
    Coordinate(17, 27, -9, 12),
    Coordinate(7, 17, -9, 12),
    Coordinate(17, 27, 12, 32),
    Coordinate(7, 17, 12, 32),
    Coordinate(-8, 1, 17, 36),
    Coordinate(-23, -14, 17, 32),
    // Europe
    Coordinate(46, 52, 10, 32),
    Coordinate(44, 48, 1, 10),
    Coordinate(60, 64, 25, 32),
    Coordinate(59, 62, 8, 15),
    Coordinate(44, 48, 1, 10),
    Coordinate(59, 62, 8, 15),
    // Australia
    Coordinate(-31, -19, 125, 144),
    Coordinate(-31, -19, 125, 144),
    Coordinate(-31, -19, 125, 144),
    // Asia
    Coordinate(42, 64, 53, 112),
    Coordinate(42, 64, 53, 112),
    Coordinate(42, 64, 53, 112),
    Coordinate(42, 64, 53, 112),
    Coordinate(58, 71, 85, 131),
    Coordinate(58, 71, 85, 131),
    Coordinate(58, 71, 85, 131),
    Coordinate(58, 71, 85, 131),
    Coordinate(61, 68, 142, 163),
    Coordinate(61, 68, 142, 163)
  )

  def randomBetween(start: Int, end: Int): Int = {
    start + scala.util.Random.nextInt(Math.abs(end - start) + 1)
  }

  def createCoordinatesOnLand(): (Double, Double) = {
    val coordinate = coordinateLimits(randomBetween(0, coordinateLimits.size - 1))
    (randomBetween(coordinate.latStart, coordinate.latEnd), randomBetween(coordinate.lngStart, coordinate.lngEnd))
  }

  def props(
             discoveryActor: ActorRef,
             reducerActor: ActorRef
           ): Props = Props(new NodeRepoActor(discoveryActor, reducerActor))
}

case class Coordinate(latStart: Int, latEnd: Int, lngStart: Int, lngEnd: Int)
